package com.bb.flow.service.imp;

import bigbeard.tools.thread.pools.ThreadPoolFactory;
import com.bb.flow.api.ExpressionCalc;
import com.bb.flow.api.FlowEndCallback;
import com.bb.flow.api.FlowStartCallback;
import com.bb.flow.api.NodeExecuteStateCallback;
import com.bb.flow.api.entries.*;
import com.bb.flow.api.exception.ParseException;
import com.bb.flow.api.type.ExecuteState;
import com.bb.flow.api.type.FlowEndType;
import com.bb.flow.config.AbstractConfigNode;
import com.bb.flow.config.ConditionInfo;
import com.bb.flow.config.FlowInfo;
import com.bb.flow.config.enumtype.NodeType;
import com.bb.flow.parser.NodeParser;
import com.bb.flow.service.FlowDriver;
import com.bb.flow.service.exception.FlowTemplateNameAlreadyExistException;
import com.bb.flow.service.exception.FlowTemplateNameNullException;
import com.bb.flow.service.exception.FlowTemplateNotFoundException;
import com.bb.flow.service.exception.FlowTextInfoNullException;
import com.bb.flow.util.Tools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

import static com.bb.flow.util.Tools.isEmptyOrNull;

/**
 * @author bigbeard
 * 流程执行器
 */
public abstract class AbstractFlowDriver implements FlowDriver {

    private final static Logger logger = LoggerFactory.getLogger(AbstractFlowDriver.class);

    /**
     * 存放所有的流程
     */
    private final Map<String, FlowInfo> flowTemplate = new HashMap<>();

    /**
     * 存放所有的启动回调通知
     */
    private final Map<Integer, FlowStartCallback> flowStartCallbackMap = new HashMap<>();
    /**
     * 流程结束回调
     */
    private final Map<Integer, FlowEndCallback> flowEndCallbackMap = new HashMap<>();
    /**
     * 流程节点执行状态通知
     */
    private final Map<Integer, NodeExecuteStateCallback> nodeExecuteStateCallbackMap = new HashMap<>();
    /**
     * 执行节点队列
     */
    private final ArrayBlockingQueue<DataChangeTemporary> exeFlowsQueue = new ArrayBlockingQueue<>(1000);

    /**
     * 存放流程实例
     */
    private final Map<String, FlowInfo> flowInstance = new HashMap<>();
    /**
     * 通知类线程池
     */
    private final transient ExecutorService notifyExecutor;
    /**
     * 节点执行线程池
     */
    private final transient ExecutorService executeNodeService;
    private boolean running = false;
    /**
     * 解析器
     */
    private NodeParser parser;


    public AbstractFlowDriver() {
        executeNodeService = ThreadPoolFactory.createThreadPool("node-execute-thread-%s");
        notifyExecutor = ThreadPoolFactory.createNotifyThreadPool("notify-thread-%s");
        takeToExecute();
    }

    /**
     * 启动流程
     *
     * @param flowTemplateName 流程模板名称
     * @param flowInstanceName 流程实例名称
     * @param context          流程上下文
     * @throws FlowTemplateNotFoundException 找不到模板
     */
    @Override
    public void startFlow(String flowTemplateName, String flowInstanceName, Object context) throws
            FlowTemplateNotFoundException, FlowTemplateNameNullException, ParseException {
        startFlow(flowTemplateName, flowInstanceName, null, context);
    }

    /**
     * 启动流程
     *
     * @param flowTemplateName 流程模版名称
     * @param flowInstanceName 流程实例名称
     * @param parent           父流程信息
     * @param context          流程上下文参数(驱动不会使用该参数,主要是用业务节点之间使用)
     * @throws FlowTemplateNotFoundException 流程不存在
     */
    @Override
    public void startFlow(String flowTemplateName, String flowInstanceName, FlowInfo parent, Object context)
            throws FlowTemplateNotFoundException, FlowTemplateNameNullException, ParseException {
        startFlow(flowTemplateName, flowInstanceName, "", parent, context);
    }

    /**
     * 从指定节点开始启动
     *
     * @param flowTemplateName 流程模板名称
     * @param flowInstanceName 流程实例名称
     * @param nodeKey          启动节点
     * @param parent           上级流程
     * @param context          流程上下文参数(驱动不会使用该参数,主要是用业务节点之间使用)
     * @throws FlowTemplateNotFoundException 流程不存在
     */
    @Override
    public void startFlow(
            String flowTemplateName, String flowInstanceName, String nodeKey, FlowInfo parent
            , Object context) throws FlowTemplateNotFoundException, FlowTemplateNameNullException, ParseException {
        FlowInfo flowInfo = createFlowInstance(flowTemplateName);
        String flowInstanceCode = flowInstanceName;
        if (isEmptyOrNull(flowInstanceName)) {
            logger.warn("流程模版[{}]实例名为空,系统将使用默认的流程实力名称", flowTemplateName);
            flowInstanceCode = createDefaultFlowInstanceName(flowTemplateName);
        } else if (flowInstance.containsKey(flowInstanceName)) {
            logger.warn("流程实例名称[{}]已经存在,系统将使用默认的流程实力名称", flowInstanceName);
            flowInstanceCode = createDefaultFlowInstanceName(flowTemplateName);
        }
        flowInfo.setFlowInstanceName(flowInstanceCode);
        flowInfo.setCancelExecute(false);
        try {
            if (null != parent) {
                flowInfo.setParentFlow(parent);
            }
        } catch (Exception e) {
            logger.error("流程[{}],发生错误,错误信息:{}", flowInfo.getFlowTemplateName(), e.getMessage());
        }
        NotifyParentFlowInfo notifyParentFlowInfo = getParent(flowInfo);

        FlowContext flowContext = new FlowContext(flowTemplateName, nodeKey, context, NodeType.NONE
                , flowInstanceCode, notifyParentFlowInfo);
        flowInfo.setFlowContext(flowContext);

        NotifyFlowStartInfo notifyFlowStartInfo = new NotifyFlowStartInfo(
                flowInfo.getFlowTemplateName()
                , flowInfo.getFlowInstanceName()
                , flowInfo.getFlowNodes(), notifyParentFlowInfo
                , context);
        notifyFlowStartInfo.setFlowOtherInfo(flowInfo.getOtherInfo());
        executeFlowStartCallback(notifyFlowStartInfo);
        List<AbstractConfigNode> firstNode = new ArrayList<>();
        if (isEmptyOrNull(nodeKey)) {
            firstNode = getFirstNode(flowInfo);
        } else {
            AbstractConfigNode abstractConfigNode = flowInfo.getFlowNodes().get(nodeKey);
            if (null != abstractConfigNode) {
                firstNode.add(abstractConfigNode);
            }
        }
        flowInstance.put(flowInfo.getFlowInstanceName(), flowInfo);
        if (firstNode.isEmpty()) {
            logger.error("流程模版[{}],找不到开始节点{},无法执行!", flowTemplateName, nodeKey);
            NotifyFlowEndInfo notifyFlowEndInfo = new NotifyFlowEndInfo(
                    flowTemplateName, flowInstanceName
                    , FlowEndType.NOT_EXECUTE, notifyParentFlowInfo, flowInfo.getFlowContext());
            notifyFlowEndInfo.setFlowOtherInfo(flowInfo.getOtherInfo());
            executeFlowEndCallback(notifyFlowEndInfo);
        } else {
            if (firstNode.size() > 1) {
                logger.warn("当前流程[{}]具有多个启动节点,系统将随机抽取一个进行执行.", flowTemplateName);
            }
            AbstractConfigNode node = firstNode.get(0);
            prepareExecute(node, flowInfo);
        }
    }

    /**
     * 停止正在执行的流程
     *
     * @param flowInstanceName 流程实例名称
     */
    @Override
    public synchronized void stopFlow(String flowInstanceName) {
        if (flowInstance.containsKey(flowInstanceName)) {
            FlowInfo flowInfo = flowInstance.get(flowInstanceName);
            flowInfo.setCancelExecute(true);
            logger.info("用户调用取消接口,停止流程模版[{}]的实例[{}]执行", flowInfo.getFlowTemplateName(), flowInstanceName);
        } else {
            logger.warn("流程实例名称[{}]不存在!", flowInstanceName);
        }
    }

    /**
     * 计算流程是否满足启动条件
     *
     * @param flowTemplateName 流程模版名称
     * @param context          上下文参数
     * @return true:满足,false不满足
     * @throws FlowTemplateNotFoundException 流程未找到
     */
    @Override
    public boolean confirmCanFlowStart(String flowTemplateName, Object context)
            throws FlowTemplateNotFoundException, FlowTemplateNameNullException, ParseException {
        FlowInfo flow = createFlowInstance(flowTemplateName);
        ConditionInfo condition = flow.getCondition();
        if (null == condition || null == condition.getExpressionCalc()) {
            logger.warn("流程[{}]没有配置启动条件或者配置的启动条件不完整,系统自动按照无条件启动", flowTemplateName);
            return true;
        }
        NotifyParentFlowInfo parent = getParent(flow);
        FlowContext flowContext = new FlowContext(flowTemplateName, null, context
                , NodeType.NONE, flow.getFlowInstanceName(), parent);
        ExpressionCalc expressionCalc = condition.getExpressionCalc();
        return expressionCalc.calc(condition.getComposeExpress(), flowContext, null);

    }

    /**
     * 添加流程
     *
     * @param flowInfo 需要添加的流程对象
     * @throws FlowTemplateNameAlreadyExistException 流程模版名称已经存在
     */
    @Override
    public void addFlowTemplate(FlowInfo flowInfo)
            throws FlowTemplateNameAlreadyExistException, FlowTextInfoNullException {
        if (null == flowInfo) {
            return;
        }
        if (isEmptyOrNull(flowInfo.getFlowTextInfo()) && flowInfo.getParentFlow() == null) {
            throw new FlowTextInfoNullException("创建此流程对象的原始信息未配置");
        }
        if (isEmptyOrNull(flowInfo.getFlowTemplateName())) {
            throw new FlowTextInfoNullException("流程模版名称为空");
        }
        FlowInfo flowTemplate = getFlowTemplate(flowInfo.getFlowTemplateName());
        if (null != flowTemplate) {
            throw new FlowTemplateNameAlreadyExistException("The flow template name ["
                    + flowInfo.getFlowTemplateName()
                    + "] have been exist!");
        }
        this.flowTemplate.put(flowInfo.getFlowTemplateName(), flowInfo);
    }


    /**
     * 添加流程模版
     *
     * @param textFlow 流程json内容
     * @throws FlowTemplateNameAlreadyExistException 流程模版名称已经存在
     */
    @Override
    public void addFlowTemplate(String textFlow) throws FlowTemplateNameAlreadyExistException,
            FlowTextInfoNullException, FlowTemplateNameNullException, ParseException {
        if (null != parser) {
            parser.setCreateNodeInstance(false);
            FlowInfo flowInfo = parser.parse(textFlow);
            if (null != flowInfo) {
                flowInfo.setFlowTextInfo(textFlow);
                this.addFlowTemplate(flowInfo);
            }
        }
    }

    /**
     * 获取流程实例
     *
     * @param flowTemplateName 流程模板名称
     * @return 流程实例
     * @throws FlowTemplateNotFoundException 流程模板未找到
     */
    @Override
    public FlowInfo createFlowInstance(String flowTemplateName) throws FlowTemplateNotFoundException, FlowTemplateNameNullException, ParseException {
        FlowInfo flowTemplate = getFlowTemplate(flowTemplateName);
        if (null == flowTemplate) {
            throw new FlowTemplateNotFoundException("The flow template name [" + flowTemplateName + "] is not exist!");
        }
        if (Tools.NULL_FLOW_TEXT_INFO.equals(flowTemplate.getFlowTextInfo())) {
            return flowTemplate;
        }
        parser.setCreateNodeInstance(true);
        FlowInfo flowInfo = parser.parse(flowTemplate.getFlowTextInfo());
        if (null == flowInfo) {
            logger.error("流程[{}]从模版创建流程对象失败,直接使用模板", flowTemplate.getFlowTemplateName());
            flowInfo = flowTemplate;
        }
        return flowInfo;
    }

    /**
     * 注册流程启动回调通知
     *
     * @param flowStartCallback 回调通知
     */
    @Override
    public void registerFlowStartCallback(FlowStartCallback flowStartCallback) {
        if (null != flowStartCallback) {
            int hashCode = flowStartCallback.hashCode();
            synchronized (flowStartCallbackMap) {
                if (!flowStartCallbackMap.containsKey(hashCode)) {
                    flowStartCallbackMap.put(hashCode, flowStartCallback);
                }
            }
        }
    }

    /**
     * 移除流程启动回调
     *
     * @param flowStartCallback 回调通知
     */
    @Override
    public void unregisterFlowStartCallback(FlowStartCallback flowStartCallback) {
        if (null != flowStartCallback) {
            int hashCode = flowStartCallback.hashCode();
            synchronized (flowStartCallbackMap) {
                flowStartCallbackMap.remove(hashCode);
            }
        }
    }

    /**
     * 注册流程节点执行状态回调
     *
     * @param callback 监听器
     */
    @Override
    public void registerNodeExecuteStateCallback(NodeExecuteStateCallback callback) {
        if (null != callback) {
            int hashCode = callback.hashCode();
            if (!nodeExecuteStateCallbackMap.containsKey(hashCode)) {
                nodeExecuteStateCallbackMap.put(hashCode, callback);
            }
        }
    }

    /**
     * 移除流程执行状态回调
     *
     * @param callback 监听器
     */
    @Override
    public void unregisterNodeExecuteStateCallback(NodeExecuteStateCallback callback) {
        if (null != callback) {
            int hashCode = callback.hashCode();
            nodeExecuteStateCallbackMap.remove(hashCode);
        }
    }

    /**
     * 注册流程执行结束监听
     *
     * @param callback 监听器
     */
    @Override
    public void registerFlowEndCallback(FlowEndCallback callback) {
        if (null != callback) {
            int hashCode = callback.hashCode();
            if (!flowEndCallbackMap.containsKey(hashCode)) {
                flowEndCallbackMap.put(hashCode, callback);
            }
        }
    }

    /**
     * 移除流程模版
     *
     * @param flowTemplateName 流程模版名称
     */
    @Override
    public void removeFlowTemplate(String flowTemplateName) {
        synchronized (flowTemplate) {
            flowTemplate.remove(flowTemplateName);
        }
    }

    /**
     * 取消注册流程执行结束监听
     *
     * @param callback 监听器
     */
    @Override
    public void unregisterFlowEndCallback(FlowEndCallback callback) {
        if (null != callback) {
            int hashCode = callback.hashCode();
            flowEndCallbackMap.remove(hashCode);
        }
    }

    /**
     * 获取流程实例
     *
     * @param instanceName 实例名称
     * @return 流程实例
     */
    @Override
    public FlowInfo getFlowInstance(String instanceName) {
        return flowInstance.get(instanceName);
    }

    /**
     * 获取全部正在运行的实例
     *
     * @return 正在运行的全部流程实例
     */
    @Override
    public List<FlowInfo> getFlowInstance() {
        return new ArrayList<>(flowInstance.values());
    }

    /**
     * 加入到缓存准备执行
     *
     * @param flowInfo 流程
     * @param node     节点
     */
    @Override
    public void prepareExecute(AbstractConfigNode node, FlowInfo flowInfo) {
        try {
            node.setExecuteState(ExecuteState.PREPARE);
            NotifyParentFlowInfo parent = getParent(flowInfo);
            NotifyNodeExecuteInfo notifyNodeExecuteData = new NotifyNodeExecuteInfo(
                    flowInfo.getFlowTemplateName()
                    , node.getNodeKey()
                    , node.getExecuteState()
                    , flowInfo.getFlowContext()
                    , node.getNodeType()
                    , flowInfo.getFlowInstanceName()
                    , parent);
            executeNodeExecuteStateCallback(notifyNodeExecuteData);

            DataChangeTemporary dataChangeTemporary = new DataChangeTemporary();
            dataChangeTemporary.setFlowInfo(flowInfo);
            dataChangeTemporary.setNode(node);
            exeFlowsQueue.put(dataChangeTemporary);
        } catch (InterruptedException ignored) {
        }
    }

    /**
     * 执行流程
     *
     * @param flowInfo 流程
     * @param node     节点
     */
    @Override
    public abstract void executeNode(AbstractConfigNode node, FlowInfo flowInfo);

    /**
     * 设置解析器
     *
     * @param parser 解析器
     */
    @Override
    public void setParser(NodeParser parser) {
        this.parser = parser;
    }

    /**
     * 获取所有流程模板名称
     *
     * @return 流程模板名称
     */
    @Override
    public List<String> getAllTemplateNames() {
        return flowTemplate.values().parallelStream().map(FlowInfo::getFlowTemplateName).collect(Collectors.toList());
    }

    private void takeToExecute() {
        if (running) {
            return;
        }
        running = true;
        String threadName = "wait-for-execute-thread-%s";
        ExecutorService singleThreadPool = ThreadPoolFactory.createSingleThreadPool(threadName);
        singleThreadPool.submit(() -> {
            while (running) {
                try {
                    DataChangeTemporary take = exeFlowsQueue.take();
                    executeNodeService.submit(() -> executeNode(take.getNode(), take.getFlowInfo()));
                } catch (InterruptedException ignored) {
                }
            }
        });
    }

    /**
     * 查找首节点
     *
     * @param flowInfo 流程
     * @return 首节点, 没找到首节点则返回空集合, 不返回null
     */
    private List<AbstractConfigNode> getFirstNode(FlowInfo flowInfo) {
        List<AbstractConfigNode> firstNodes = new ArrayList<>();
        flowInfo.getFlowNodes().values().parallelStream()
                .filter(AbstractConfigNode::isStartNode)
                .forEach(firstNodes::add);
        return firstNodes;
    }

    /**
     * 获取下一个节点
     *
     * @param currentNode 当前节点
     * @param flowInfo    当前流程
     * @return 下一节点, null表示没有下一节点
     */
    protected AbstractConfigNode getDefaultNextNodeKey(AbstractConfigNode currentNode, FlowInfo flowInfo) {
        if (userCancelExecute(flowInfo)) {
            return null;
        }
        String nodeKey = currentNode.getNextNodeKey();
        return flowInfo.getFlowNodes().get(nodeKey);
    }

    /**
     * 开始通知节点执行状态
     *
     * @param notifyNodeExecuteData 状态数据
     */
    protected void executeNodeExecuteStateCallback(NotifyNodeExecuteInfo notifyNodeExecuteData) {
        Map<Integer, NodeExecuteStateCallback> executeMap = new HashMap<>(nodeExecuteStateCallbackMap);
        executeMap.values().forEach(x -> notifyExecutor.submit(() -> x.notifyState(notifyNodeExecuteData)));
    }

    /**
     * 开始通知流程执行完毕
     *
     * @param info 流程结束时的相关信息
     */
    protected synchronized void executeFlowEndCallback(NotifyFlowEndInfo info) {

        Map<Integer, FlowEndCallback> executeMap = new HashMap<>(flowEndCallbackMap);
        FlowInfo flowInfo = getFlowInstance(info.getFlowInstanceName());
        if (null == flowInfo) {
            logger.error("在流程实例列表中找不到[{}]流程实例无法通知结束事件", info.getFlowInstanceName());
            return;
        }
        if (info.getFlowEndType() == FlowEndType.EXCEPTION_END
                || info.getFlowEndType() == FlowEndType.NOT_EXECUTE
                || info.getFlowEndType() == FlowEndType.USER_CANCEL) {
            removeInstance(flowInfo.getFlowInstanceName());
            executeMap.values().forEach(x -> notifyExecutor.submit(() -> x.notifyFlowEnd(info)));
            return;
        }
        //统计当前流程是否还有节点正在执行或者是准备执行
        long count = flowInfo.getFlowNodes().values().parallelStream().filter(
                x -> x.getExecuteState() == ExecuteState.EXECUTING
                        || x.getExecuteState() == ExecuteState.PREPARE).count();

        boolean cancel = checkUserCancel(flowInfo);

        if (count <= 0 || cancel) {
            removeInstance(flowInfo.getFlowInstanceName());
            executeMap.values().forEach(x -> notifyExecutor.submit(() -> x.notifyFlowEnd(info)));
        }
    }

    protected NotifyParentFlowInfo getParent(FlowInfo flowInfo) {
        NotifyParentFlowInfo parentFlowInfo = null;
        FlowInfo parentFlow = flowInfo.getParentFlow();
        if (null != parentFlow) {
            parentFlowInfo = new NotifyParentFlowInfo(parentFlow.getFlowInstanceName(),
                    parentFlow.getFlowTemplateName());
            parentFlow = parentFlow.getParentFlow();
            NotifyParentFlowInfo cur = parentFlowInfo;
            while (null != parentFlow) {
                NotifyParentFlowInfo tmp = new NotifyParentFlowInfo(
                        parentFlow.getFlowInstanceName(),
                        parentFlow.getFlowTemplateName());
                tmp.setParent(cur);
                tmp.setFlowOtherInfo(parentFlow.getOtherInfo());
                cur = tmp;
                parentFlow = parentFlow.getParentFlow();
            }
        }
        return parentFlowInfo;
    }

    /**
     * 开始通知流程开始执行
     *
     * @param notifyFlowStartInfo 流程开始执行的上下文信息
     */
    private void executeFlowStartCallback(NotifyFlowStartInfo notifyFlowStartInfo) {
        Map<Integer, FlowStartCallback> executeMap = new HashMap<>(flowStartCallbackMap);
        executeMap.values().forEach(x -> notifyExecutor.submit(() -> x.notifyFlowStart(notifyFlowStartInfo)));
    }

    /**
     * 检查流程是否被标记成取消
     *
     * @param flowInfo 流程信息
     * @return true:取消
     */
    private boolean checkUserCancel(FlowInfo flowInfo) {
        boolean cancel = flowInfo.isCancelExecute();
        FlowInfo parentFlow = flowInfo.getParentFlow();
        while (parentFlow != null && !cancel) {
            cancel = parentFlow.isCancelExecute();
            parentFlow = parentFlow.getParentFlow();
        }
        return cancel;
    }

    /**
     * 发出用户取消执行的通知
     *
     * @param flowInfo 流程信息
     * @return true取消执行, fasle 继续执行
     */
    protected boolean userCancelExecute(FlowInfo flowInfo) {

        boolean cancel = checkUserCancel(flowInfo);
        if (cancel) {
            NotifyParentFlowInfo parent = getParent(flowInfo);
            NotifyFlowEndInfo notifyFlowEndInfo = new NotifyFlowEndInfo(
                    flowInfo.getFlowTemplateName()
                    , flowInfo.getFlowInstanceName()
                    , FlowEndType.USER_CANCEL
                    , parent
                    , flowInfo.getFlowContext()
            );
            notifyFlowEndInfo.setFlowOtherInfo(flowInfo.getOtherInfo());
            executeFlowEndCallback(notifyFlowEndInfo);
            logger.info("用户取消流程[{}]的实例[{}]执行", flowInfo.getFlowTemplateName(), flowInfo.getFlowInstanceName());
            return true;
        }
        return false;
    }

    /**
     * 从流程模版队列中读取流程信息
     *
     * @param templateName 流程模版名称
     * @return 流程信息
     */
    private FlowInfo getFlowTemplate(String templateName) {
        return flowTemplate.get(templateName);
    }

    private String createDefaultFlowInstanceName(String flowTemplateName) {
        return String.format("%s_instance_%d", flowTemplateName, System.currentTimeMillis());
    }

    private void removeInstance(String instanceName) {
        flowInstance.remove(instanceName);
    }

    /**
     * 临时数据交换使用
     */
    private static class DataChangeTemporary {
        AbstractConfigNode node;
        FlowInfo flowInfo;

        public AbstractConfigNode getNode() {
            return node;
        }

        public void setNode(AbstractConfigNode node) {
            this.node = node;
        }

        public FlowInfo getFlowInfo() {
            return flowInfo;
        }

        public void setFlowInfo(FlowInfo flowInfo) {
            this.flowInfo = flowInfo;
        }
    }
}
